package com.maiko.usercenter.manager;


import com.maiko.usercenter.common.ErrorCode;
import com.maiko.usercenter.exception.BusinessException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
 * Redis限流管理器
 * @author Maiko7
 * @create 2025-07-14 14:40
 */
@Component
@Slf4j
public class RedisLimiterManager {

    /**
     * 注入StringRedisTemplate模板，用于操作Redis中的字符串值
     */
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    /**
     * 执行限流操作
     *
     * @param key 限流键，用于标识限流对象
     * @param permits 允许的最大请求数
     * @param time 时间窗口的持续时间
     * @param unit 时间单位
     */
    public void doRateLimitByRedisCounter(String key, long permits, long time, TimeUnit unit) {
        // 增加限流键的计数
        Long count = stringRedisTemplate.opsForValue().increment(key);
        // 如果是新键，设置过期时间
        if (count == 1) {
            stringRedisTemplate.expire(key, time, unit);
        }

        // 检查当前计数是否超过最大允许数
        if (count != null && count > permits) {
            // 记录限流日志
            log.warn("限流触发：key={}, 当前请求数={}", key, count);
            // 抛出业务异常，提示请求过于频繁
            throw new BusinessException(ErrorCode.TOO_MANY_REQUEST, "请求频繁，请稍后再试");
        }
    }

    /**
     * 使用 Redisson 令牌桶算法实现限流
     *
     * @param key     限流标识（业务标识+用户ID/IP等）
     * @param permits 最大允许请求数（时间窗口内令牌数）
     * @param time    时间窗口长度
     * @param unit    时间单位
     */
    public void doRateLimitByRedisson(String key, long permits, long time, TimeUnit unit) {
        // 获取限流器
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);

        // 设置限流规则（整个时间窗口内允许的最大请求数）
        // 这里使用 RateType.OVERALL，所有客户端共用一个限流器
        /**
         * 设置分布式限流规则，表示在指定时间窗口内允许的最大请求数。
         *
         * 参数说明：
         * 1. RateType.OVERALL：表示整体速率限制模式，适用于所有调用该资源的客户端。
         *    其他可选类型如 PER_CLIENT，则表示每个客户端独立计数。
         * 2. permits：每单位时间允许的最大请求次数。
         *    示例：若 permits=5，表示最多允许5次请求通过。
         * 3. Duration.of(time, unit.toChronoUnit())：定义时间窗口大小。
         *    示例：time=1, unit=MINUTES → 时间窗口为1分钟。
         *
         *
         * 使用示例：
         * 假设你有一个接口被多个用户并发访问，你可以使用如下注解进行限流控制：
         *
         *     @RateLimiter(key = "user_login", permits = 5, time = 1, unit = TimeUnit.MINUTES)
         *     public void login(String username) {
         *         // 用户登录逻辑
         *     }
         *
         * 上述配置表示：每1分钟内，每个客户端最多只能尝试登录5次（根据 key=user_login 判断）。
         */
        rateLimiter.trySetRate(RateType.OVERALL, permits, Duration.of(time, unit.toChronoUnit()));

        // 尝试获取一个令牌
        // 尝试获取限流令牌
        boolean canPass = rateLimiter.tryAcquire(1);
        if (!canPass) {
            // 如果获取失败，则记录日志并抛出异常，阻止方法执行
            log.warn("触发限流：key={}, permits={}, time={}, unit={}", key, permits, time, unit);
            throw new BusinessException(ErrorCode.TOO_MANY_REQUEST, "请求过于频繁，请稍后再试");
        }
        // 如果获取成功，则继续执行原方法

    }
}


